package bsk;

/**
 * @author jd
 */
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

/**
 * generator certyfikatu
 */
public class CertGen {

    // generuj v1 cert, cert serwera, jesli nie ma
    public static X509Certificate generateV1Certificate(KeyPair pair) throws InvalidKeyException, NoSuchProviderException, SignatureException {

        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
        certGen.setIssuerDN(new X500Principal("CN=PKW Certificate"));
        certGen.setNotBefore(new Date(System.currentTimeMillis() - 10000));
        certGen.setNotAfter(new Date(System.currentTimeMillis() + 10000));
        certGen.setSubjectDN(new X500Principal("CN=PKW Certificate"));
        certGen.setPublicKey(pair.getPublic());
        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");

        return certGen.generateX509Certificate(pair.getPrivate(), "BC");
    }

    //
    public static KeyPair generateRSAKeyPair() throws Exception {

        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
        kpGen.initialize(1024, new SecureRandom());

        return kpGen.generateKeyPair();
    }

    public static void SaveKeyPair(KeyPair keyPair) throws IOException {
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();

        // Store Public Key.
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
                publicKey.getEncoded());
        FileOutputStream fos = new FileOutputStream("public.key");
        fos.write(x509EncodedKeySpec.getEncoded());
        fos.close();

        // Store Private Key.
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
                privateKey.getEncoded());
        fos = new FileOutputStream("private.key");
        fos.write(pkcs8EncodedKeySpec.getEncoded());
        fos.close();
    }

    public static KeyPair LoadKeyPair()
            throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException {

        String algorithm = "RSA";

        // Read Public Key.
        File filePublicKey = new File("public.key");
        FileInputStream fis = new FileInputStream("public.key");
        byte[] encodedPublicKey = new byte[(int) filePublicKey.length()];
        fis.read(encodedPublicKey);
        fis.close();

        // Read Private Key.
        File filePrivateKey = new File("private.key");
        fis = new FileInputStream("private.key");
        byte[] encodedPrivateKey = new byte[(int) filePrivateKey.length()];
        fis.read(encodedPrivateKey);
        fis.close();

        // Generate KeyPair.
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
                encodedPublicKey);
        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
                encodedPrivateKey);
        PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

        return new KeyPair(publicKey, privateKey);
    }

    //get cert serwera
    public static X509Certificate assignCert() throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, CertificateParsingException, InvalidKeyException {

        X509Certificate cert = null;

        try {
            cert = convert(getServCert());
        } catch (CertificateExpiredException ex) {
            Logger.getLogger(CertGen.class.getName()).log(Level.SEVERE, null, ex);
        } catch (CertificateNotYetValidException ex) {
            Logger.getLogger(CertGen.class.getName()).log(Level.SEVERE, null, ex);
        } catch (CertificateException ex) {
            Logger.getLogger(CertGen.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchProviderException ex) {
            Logger.getLogger(CertGen.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SignatureException ex) {
            Logger.getLogger(CertGen.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(CertGen.class.getName()).log(Level.SEVERE, null, ex);
        } catch (javax.security.cert.CertificateException ex) {
            Logger.getLogger(CertGen.class.getName()).log(Level.SEVERE, null, ex);
        }

        return cert;
    }

    // zapisz cert do pliku/
    public static boolean saveCert(X509Certificate cert2) throws CertificateEncodingException, IOException, CertificateExpiredException, CertificateNotYetValidException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, javax.security.cert.CertificateExpiredException, javax.security.cert.CertificateNotYetValidException {
        javax.security.cert.X509Certificate cert = convert2(cert2);

        FileOutputStream sigfos = null;

        try {

            sigfos = new FileOutputStream("serv_cert.crt");
            byte[] byte_cert = cert.getEncoded();
            sigfos.write(byte_cert);

        } catch (FileNotFoundException ex) {
        } finally {
            try {

                cert.checkValidity(new Date());
                sigfos.close();

                return true;

            } catch (IOException ex) {

                return false;
            }
        }
    }

    // wczytaj cert z pliku
    public static javax.security.cert.X509Certificate getServCert() throws CertificateExpiredException, CertificateNotYetValidException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, FileNotFoundException, javax.security.cert.CertificateException {

        InputStream inStream = new FileInputStream("serv_cert.crt");
        javax.security.cert.X509Certificate cert = javax.security.cert.X509Certificate.getInstance(inStream);

        return cert;
    }

    //
    public static PKCS10CertificationRequest getCertReq(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {

        String signatureAlgorithm = "SHA1withDSA";

        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        PKCS10CertificationRequest certReq = new PKCS10CertificationRequest(
                signatureAlgorithm,
                new X500Principal("CN=Anonim, OU=Wyborca, O=Unknown, L=Unknown, ST=Unknown, C=PL"),
                keyPair.getPublic(),
                null,
                keyPair.getPrivate());

        return certReq;
    }

    //
    public static X509Certificate getNewCert(PKCS10CertificationRequest certReq) throws CertificateParsingException, InvalidKeyException, CertificateEncodingException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException, Exception {

        PKCS10CertificationRequest request = certReq;
        X509Certificate root = convert(getServCert());

        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
        certGen.setIssuerDN(root.getSubjectX500Principal());
        certGen.setNotBefore(new Date(System.currentTimeMillis()));
        certGen.setNotAfter(new Date(System.currentTimeMillis() + 7 * 24 * 3600000));
        certGen.setSubjectDN(request.getCertificationRequestInfo().getSubject());
        certGen.setPublicKey(request.getPublicKey("BC"));
        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");

        certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                new AuthorityKeyIdentifierStructure(root));

        certGen.addExtension(X509Extensions.SubjectKeyIdentifier,
                false, new SubjectKeyIdentifierStructure(request.getPublicKey("BC")));

        certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));

        certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
                | KeyUsage.keyEncipherment));

        certGen.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(
                KeyPurposeId.id_kp_serverAuth));

        ASN1Set attributes = request.getCertificationRequestInfo().getAttributes();
        if (attributes != null) {
            for (int i = 0; i != attributes.size(); i++) {
                Attribute attr = Attribute.getInstance(attributes.getObjectAt(i));

                if (attr.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) {
                    X509Extensions extensions = X509Extensions.getInstance(attr.getAttrValues().getObjectAt(0));

                    Enumeration e = extensions.oids();
                    while (e.hasMoreElements()) {
                        DERObjectIdentifier oid = (DERObjectIdentifier) e.nextElement();
                        X509Extension ext = extensions.getExtension(oid);

                        certGen.addExtension(oid, ext.isCritical(), ext.getValue().getOctets());
                    }
                }
            }
        }

        X509Certificate issuedCert = certGen.generateX509Certificate(LoadKeyPair().getPrivate());

        return issuedCert;
    }

    public static String getExtensionValue(X509Certificate X509Certificate, String oid) throws IOException {
        String decoded = null;
        byte[] extensionValue = X509Certificate.getExtensionValue(oid);

        if (extensionValue != null) {
            DERObject derObject = toDERObject(extensionValue);
            if (derObject instanceof DEROctetString) {
                DEROctetString derOctetString = (DEROctetString) derObject;

                derObject = toDERObject(derOctetString.getOctets());
                if (derObject instanceof DERUTF8String) {
                    DERUTF8String s = DERUTF8String.getInstance(derObject);
                    decoded = s.getString();
                }

            }
        }
        return decoded;
    }

    public static DERObject toDERObject(byte[] data) throws IOException {
        ByteArrayInputStream inStream = new ByteArrayInputStream(data);
        ASN1InputStream asnInputStream = new ASN1InputStream(inStream);

        return asnInputStream.readObject();
    }

    // Converts to java.security
    public static java.security.cert.X509Certificate convert(javax.security.cert.X509Certificate cert) {
        try {
            byte[] encoded = cert.getEncoded();
            ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
            java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
            return (java.security.cert.X509Certificate) cf.generateCertificate(bis);
        } catch (java.security.cert.CertificateEncodingException e) {
        } catch (javax.security.cert.CertificateEncodingException e) {
        } catch (java.security.cert.CertificateException e) {
        }
        return null;
    }

    // Converts to javax.security
    public static javax.security.cert.X509Certificate convert2(java.security.cert.X509Certificate cert) {
        try {
            byte[] encoded = cert.getEncoded();
            return javax.security.cert.X509Certificate.getInstance(encoded);
        } catch (java.security.cert.CertificateEncodingException e) {
        } catch (javax.security.cert.CertificateEncodingException e) {
        } catch (javax.security.cert.CertificateException e) {
        }
        return null;
    }

    public static void verifyCertificate(X509Certificate x509)
            throws CertificateParsingException, CertificateExpiredException, CertificateNotYetValidException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, FileNotFoundException, javax.security.cert.CertificateException {
        Principal p;
        p = x509.getIssuerDN();
        try {
            verifySigner(x509, p.getName());
        } catch (CertificateException ce) {
            System.out.println("We don't know the certificate signer");
        }
        try {
            x509.checkValidity();
        } catch (CertificateExpiredException cee) {
            System.out.println("That certificate is no longer valid");
        } catch (CertificateNotYetValidException cnyve) {
            System.out.println("That certificate is not yet valid");
        }
    }

    private static void verifySigner(X509Certificate c, String name)
            throws CertificateException, CertificateExpiredException, CertificateNotYetValidException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, FileNotFoundException, javax.security.cert.CertificateException {
        //Certificate 
        X509Certificate issuerCert = null;
        X509Certificate sCert = convert(getServCert());
        KeyStore ks = null;

        if (name.equals(sCert.getSubjectDN().getName())) {
            issuerCert = sCert;
        }

        if (issuerCert == null) {
            throw new CertificateException("No such certificate");
        }
        try {
            c.verify(issuerCert.getPublicKey());
        } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) {
            throw new CertificateException(e.toString());
        }
    }

    // store certifiate serial number
    public static void saveSN(List<PublicKey> snList) {
        try {
            //use buffering
            OutputStream file = new FileOutputStream("sn.sn");
            OutputStream buffer = new BufferedOutputStream(file);
            ObjectOutput output = new ObjectOutputStream(buffer);
            try {
                output.writeObject(snList);
            } finally {
                output.close();
            }
        } catch (IOException ex) {
        }
    }

    // load certifiate serial number
    public static List<PublicKey> loadSN() {
        List<PublicKey> snList = null;
        try {
            //use buffering
            InputStream file = new FileInputStream("quarks.ser");
            InputStream buffer = new BufferedInputStream(file);
            ObjectInput input = new ObjectInputStream(buffer);
            try {
                //deserialize the List
                snList = (List<PublicKey>) input.readObject();
            } finally {
                input.close();
            }
        } catch (ClassNotFoundException ex) {
        } catch (IOException ex) {
        }
        return snList;
    }

    public static boolean findPublicKey(PublicKey pk, List<PublicKey> keyList)
    {
        return keyList.contains(pk);
    }
}
